package com.github.wxiaoqi.security.gate.v2.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.wxiaoqi.security.api.vo.authority.PermissionInfo;
import com.github.wxiaoqi.security.api.vo.log.LogInfo;
import com.github.wxiaoqi.security.auth.client.config.ServiceAuthConfig;
import com.github.wxiaoqi.security.auth.client.config.UserAuthConfig;
import com.github.wxiaoqi.security.auth.client.jwt.ServiceAuthUtil;
import com.github.wxiaoqi.security.auth.client.jwt.UserAuthUtil;
import com.github.wxiaoqi.security.auth.common.util.jwt.IJWTInfo;
import com.github.wxiaoqi.security.common.context.BaseContextHandler;
import com.github.wxiaoqi.security.common.msg.BaseResponse;
import com.github.wxiaoqi.security.common.msg.ResponseCode;
import com.github.wxiaoqi.security.common.msg.auth.TokenForbiddenResponse;
import com.github.wxiaoqi.security.common.util.ClientUtil;
import com.github.wxiaoqi.security.common.util.EntityUtils;
import com.github.wxiaoqi.security.common.util.FilterUtil;
import com.github.wxiaoqi.security.common.util.ValidateSign;
import com.github.wxiaoqi.security.gate.v2.GatewayServerBootstrap;
import com.github.wxiaoqi.security.gate.v2.feign.ILogService;
import com.github.wxiaoqi.security.gate.v2.feign.IUserService;
import com.github.wxiaoqi.security.gate.v2.utils.DBLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpHead;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.result.view.RequestContext;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author ace
 * @create 2018/3/12.
 */
@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
    @Autowired
    @Lazy
    private IUserService userService;
    @Autowired
    @Lazy
    private ILogService logService;

    @Value("${gate.ignore.startWith}")
    private String startWith;
    /**
     * crm拦截地址 
     * */
    @Value("${gate.crm.startWith}")
    private String crmApiStartWith;
    /**
     * biz拦截地址 
     * */
    @Value("${gate.biz.startWith}")
    private String bizApiStartWith;
    @Autowired
    private RedisTemplate redisTemplate;
    //    @Value("${zuul.prefix}")
//    private String zuulPrefix;

    private static final String GATE_WAY_PREFIX = "/api";
    @Autowired
    private UserAuthUtil userAuthUtil;

    @Autowired
    private ServiceAuthConfig serviceAuthConfig;

    @Autowired
    private UserAuthConfig userAuthConfig;

    @Autowired
    private ServiceAuthUtil serviceAuthUtil;
    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
        //log.info("check token and user permission....");
        LinkedHashSet requiredAttribute = serverWebExchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        ServerHttpRequest request = serverWebExchange.getRequest();
        String requestUri = request.getPath().pathWithinApplication().value();
        if (requiredAttribute != null) {
            Iterator<URI> iterator = requiredAttribute.iterator();
            while (iterator.hasNext()){
                URI next = iterator.next();
                if(next.getPath().startsWith(GATE_WAY_PREFIX)){
                    requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());
                }
            }
        }
        final String method = request.getMethod().toString();
        BaseContextHandler.setToken(null);
        ServerHttpRequest.Builder mutate = request.mutate();
        // 申请客户端密钥头
        mutate.header(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken());
        ServerHttpRequest build = mutate.build();
        // 不进行拦截的地址
        if (isStartWith(requestUri)) {
            return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
        }
        IJWTInfo user = null;
        try {
            user = getJWTUser(request, mutate);
        } catch (Exception e) {
            log.error("用户Token过期异常", e);
            return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Forbidden or Expired!"));
        }
        boolean istokenvalid=false;
        if(requestUri.startsWith(crmApiStartWith) || requestUri.startsWith(bizApiStartWith)){
        	/**
        	 * 验证token
        	 * */
        	try {
				istokenvalid=checkToken(user.getUniqueName(),request);
				if(!istokenvalid){
					log.info("验证比对Token数据不一致: redis缓存username ---"+user.getUniqueName()+"--password--"+user.getPassword());
					return getVoidMono(serverWebExchange, new BaseResponse(ResponseCode.TOKEN_ISINVALID.getCode(),ResponseCode.TOKEN_ISINVALID.getMessage(),false));
	        	}
			} catch (Exception e) {
				log.info("验证Token数据报错:"+e.getMessage());
				return getVoidMono(serverWebExchange, new BaseResponse(ResponseCode.TOKEN_ISINVALID.getCode(),ResponseCode.TOKEN_ISINVALID.getMessage(),false));
			}
        	/**
        	 * 获取请求参数
        	 * 验证验签
        	 * *//*
        	String requstParams="";//EntityUtils.getBodyData((HttpServletRequest)request);
        	if(StringUtils.isEmpty(requstParams)){
				return getVoidMono(serverWebExchange, new BaseResponse(ResponseCode.PARAM_NOTNULL.getCode(),ResponseCode.PARAM_NOTNULL.getMessage(),false));
        	}
        	Map<String, Object>  decrypqustMap= JSON.parseObject(requstParams);
        	String sign= String.valueOf(decrypqustMap.get("sign"));
        	if(StringUtils.isEmpty(sign)){
				return getVoidMono(serverWebExchange, new BaseResponse(ResponseCode.SIGN_NOTNULL.getCode(),ResponseCode.SIGN_NOTNULL.getMessage(),false));
        	}
        	//签名用的盐值  在配置文件中配置
        	String key="";
        	if(!ValidateSign.checkSign(sign, key, decrypqustMap)){
        		log.info("网关校验传输参数-签名失败:"+"--之前的签名:--"+sign+"---签名盐值--"+key+"---排序后的参数---"+FilterUtil.createLinkString(decrypqustMap));
				return getVoidMono(serverWebExchange, new BaseResponse(ResponseCode.SIGN_NOTNULL.getCode(),ResponseCode.SIGN_NOTNULL.getMessage(),false));
			}*/
        }else{
        	 List<PermissionInfo> permissionIfs = userService.getAllPermissionInfo();
             // 判断资源是否启用权限约束
             Stream<PermissionInfo> stream = getPermissionIfs(requestUri, method, permissionIfs);
             List<PermissionInfo> result = stream.collect(Collectors.toList());
             PermissionInfo[] permissions = result.toArray(new PermissionInfo[]{});
             if (permissions.length > 0 ) {
                 if (checkUserPermission(permissions, serverWebExchange, user)) {
                     return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Forbidden!Does not has Permission!"));
                 }
             }else {
             	return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Forbidden!Does not has Permission!"));
     		}
        }
       
        return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());

    }

    /**
     * 网关抛异常
     *
     * @param body
     */
    @NotNull
    private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, BaseResponse body) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
        byte[] bytes = JSONObject.toJSONString(body).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    /**
     * 验证当前应用的token是否正确:应用传参过来的token和缓存中的token比对
     * 
     * @author myq
     * */
    private boolean checkToken(String username,ServerHttpRequest request)throws Exception{
    	 HttpHeaders head=request.getHeaders();
    	 List<String> authToken = head.get(userAuthConfig.getTokenHeader());
    	 String cacheAuthToken=String.valueOf(redisTemplate.opsForValue().get("login:token_"+username));
    	 if("".equals(cacheAuthToken)||null==cacheAuthToken){
    		 return false;
    	 }
    	 if(authToken.get(0).equals(cacheAuthToken)){
    		 return true;
    	 }
    	 return false;
    }
    /**
     * 获取目标权限资源
     *
     * @param requestUri
     * @param method
     * @param serviceInfo
     * @return
     */
    private Stream<PermissionInfo> getPermissionIfs(final String requestUri, final String method, List<PermissionInfo> serviceInfo) {
        return serviceInfo.parallelStream().filter(new Predicate<PermissionInfo>() {
            @Override
            public boolean test(PermissionInfo permissionInfo) {
                String uri = permissionInfo.getUri();
                if (uri.indexOf("{") > 0) {
                    uri = uri.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
                }
                String regEx = "^" + uri + "$";
                System.out.println(requestUri);
                System.out.println(regEx);
                System.out.println((Pattern.compile(regEx).matcher(requestUri).find()) && method.equals(permissionInfo.getMethod()));
                return (Pattern.compile(regEx).matcher(requestUri).find())
                        && method.equals(permissionInfo.getMethod());
            }
        });
    }

    private void setCurrentUserInfoAndLog(ServerWebExchange serverWebExchange, IJWTInfo user, PermissionInfo pm) {
        String host = serverWebExchange.getRequest().getRemoteAddress().toString();
        LogInfo logInfo = new LogInfo(pm.getMenu(), pm.getName(), pm.getUri(), new Date(), user.getId(), user.getName(), host);
        DBLog.getInstance().setLogService(logService).offerQueue(logInfo);
    }

    /**
     * 返回session中的用户信息
     *
     * @param request
     * @param ctx
     * @return
     */
    private IJWTInfo getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
        List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader());
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0);
        }
        if (StringUtils.isBlank(authToken)) {
            strings = request.getQueryParams().get("token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }
        ctx.header(userAuthConfig.getTokenHeader(), authToken);
        BaseContextHandler.setToken(authToken);
        return userAuthUtil.getInfoFromToken(authToken);
    }


    private boolean checkUserPermission(PermissionInfo[] permissions, ServerWebExchange ctx, IJWTInfo user) {
        List<PermissionInfo> permissionInfos = userService.getPermissionByUsername(user.getUniqueName());
        PermissionInfo current = null;
        for (PermissionInfo info : permissions) {
            boolean anyMatch = permissionInfos.parallelStream().anyMatch(new Predicate<PermissionInfo>() {
                @Override
                public boolean test(PermissionInfo permissionInfo) {
                    return permissionInfo.getCode().equals(info.getCode());
                }
            });
            if (anyMatch) {
                current = info;
                break;
            }
        }
        if (current == null) {
            return true;
        } else {
            if (!RequestMethod.GET.toString().equals(current.getMethod())) {
                setCurrentUserInfoAndLog(ctx, user, current);
            }
            return false;
        }
    }


    /**
     * URI是否以什么打头
     *
     * @param requestUri
     * @return
     */
    private boolean isStartWith(String requestUri) {
        boolean flag = false;
        for (String s : startWith.split(",")) {
            if (requestUri.startsWith(s)) {
                return true;
            }
        }
        return flag;
    }

    /**
     * 网关抛异常
     *
     * @param body
     * @param code
     */
    private Mono<Void> setFailedRequest(ServerWebExchange serverWebExchange, String body, int code) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
        return serverWebExchange.getResponse().setComplete();
    }
    
    public static void main(String[] args) {
    	String regEx = "^/admin/role/[a-zA-Z\\d]+/authority/element/add$";
        System.out.println(Pattern.compile(regEx).matcher("/admin/role/1/authority/element/add").find());
    }

}